/*************************************************************************************
 * Nombre del archivo: FileSystem.java
 * Universidad del Valle de Guatemala
 * Curso: Sistemas Operativos
 * @author martin
 *          Martín Guzmán 08041
 * Fecha: Mayo de 2010
 * Descripción:
*************************************************************************************/
import java.io.File;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Array;
import java.util.LinkedList;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;

public class FileSystem {

    public static final boolean debug = true;

    public static final String path_FS = "/home/martin/Desktop/Sistemas_operativos/FileSystem/FS.txt";
    public static final String path_FS_fuente = "/home/martin/Desktop/Sistemas_operativos/FileSystem/FS.txt";
    public static final int Cantidad_Bloques_FAT = 1024; //especifica la cantidad de bloques del FileSystem
    public static final int Cantidad_Bloques_Datos = 1000; //especifica la cantidad de bloques del FileSystem
    public static final int Tamano_Bloque = 1024; //especifica el tamaño de cada bloque
    public static final int Cantidad_FCB = 84; //especifica la cantidad de File Control Blocks del FileSystem
    
    FCB[] Directorio = new FCB[Cantidad_FCB]; //ya que son 84 FCB en el directorio
    String[] FAT = new String[Cantidad_Bloques_FAT]; //File Allocation Table (FAT)
    byte[][] Datos = new byte[this.Cantidad_Bloques_Datos][this.Tamano_Bloque];
    //String[] Datos = new String[Cantidad_Bloques_Datos]; //Datos

    File FS_archivo = new File(path_FS);
    File FS_archivo_fuente = new File(path_FS_fuente);

    RandomAccessFile file;

    /***********************************************
     * Método: Memoria
     * Uso: constructor
    ***********************************************/
    public FileSystem(){
        //trata de cargar uno existente
        boolean se_cargo = CargarFS();
        //si no existe crea uno default y lo guarda
        if(!se_cargo){
            PrintDebug("FileSystem.FileSystem: no se cargó el archivo");
            defaultFS();
            flushFS();
        }

    }

    /***********************************************
     * Método: flushFS
     * Uso: actualiza lo que esté en FS.toString()
    ***********************************************/
    public void flushFS() {
        //FileWriter TextOut;
        try {
            file = new RandomAccessFile(this.FS_archivo,"rw");
            file.write(this.toBytes());
            //TextOut = new FileWriter(this.FS_archivo, false);
            //TextOut.write(new String(this.toBytes()));
            //TextOut.flush();
        } catch (Exception ex) {
            PrintDebug("FileSystem.flushFS: error al guardar el FS");
        }
    }

    /***********************************************
     * Método: CargarFS
     * Uso: Intenta cargar un FS especificado por
     *      el archivo con path: path_FS
     * @return boolean si se cargó o no
    ***********************************************/
    public boolean CargarFS() {
        //---------------------------------------------------
        // obtener la cadena del archivo
        String FS_cadena = "";
        byte[] bytes;
        try {
            file = new RandomAccessFile(this.FS_archivo_fuente,"rw");
            bytes = new byte[(int)file.length()];
            file.readFully(bytes);
            FS_cadena = new String(bytes);
        } catch (Exception ex) {
            PrintDebug("FileSystem.CargarFS: El archivo de FS no se pudo abrir");
            return false;
        }
        //---------------------------------------------------
        PrintDebug("FileSystem.CargarFS: tamaño de cadena del archivo: ."+FS_cadena.length()+".");

        //---------------------------------------------------
        // revisar tamaño, debe ser de 1024*1024
        //---------------------------------------------------
        //if(FS_cadena.length()!=1024*1024){
        if(bytes.length!=1024*1024){
            PrintDebug("FileSystem.CargarFS: El tamaño del FS es incorrecto, tamaño encontrado: "+bytes.length+". Tamaño requerido: "+(1024*1024)+". Diferencia: "+(1024*1024-bytes.length));
            return false;
        }
        //---------------------------------------------------
        byte[] direc = new byte[256*this.Cantidad_FCB];
        for(int i=0;i<(256*this.Cantidad_FCB);i++){
            direc[i] = bytes[i];
        }
        String directorio = new String(direc);
        Println("FileSystem.CargarFS: Tamaño del directorio: "+directorio.length());
        //---------------------------------------------------
        // inicializar Directorio
        try{
            for(int i=0;i<this.Cantidad_FCB;i++){
                Directorio[i] = new FCB();
                String cadena = directorio.substring(0, 256);
                boolean se_cargo = Directorio[i].FCB(cadena);
                if(!se_cargo){
                    PrintDebug("FileSystem.CargarFS: No se logró cargar un directorio. i = "+i);
                    return false;
                }
                if(directorio.isEmpty()){
                    break;
                }
                directorio = directorio.substring(256);
            }
        }catch(Exception exp){
            PrintDebug("FileSystem.CargarFS: No se logró cargar el directorio");
            return false;
        }
        //---------------------------------------------------
        //---------------------------------------------------
        byte[] fat_bytes = new byte[3*this.Cantidad_Bloques_FAT];
        int a = 0;
        for(int i=(256*this.Cantidad_FCB);i<((256*this.Cantidad_FCB)+3*this.Cantidad_Bloques_FAT);i++){
            fat_bytes[a] = bytes[i];
            a++;
        }
        String fat_string = new String(fat_bytes);
        Println("FileSystem.CargarFS: Tamaño del FAT: "+fat_string.length());
        //---------------------------------------------------
        //---------------------------------------------------
        // inicializar FAT
        try{
            for(int i=0;i<this.Cantidad_Bloques_FAT;i++){
                String cadena = fat_string.substring(0, 3);
                boolean se_cargo = this.setFAT(i, cadena);
                if(!se_cargo){
                    PrintDebug("FileSystem.CargarFS: No se logró cargar un fat. i = "+i+" cadena: ."+cadena+".");
                    return false;
                }
                if(fat_string.isEmpty()){
                    break;
                }
                fat_string = fat_string.substring(3);
            }
        }catch(Exception exp){
            PrintDebug("FileSystem.CargarFS: No se logró cargar el FAT");
            return false;
        }
        //---------------------------------------------------
        //---------------------------------------------------
        byte[] datos_bytes = new byte[this.Tamano_Bloque*this.Cantidad_Bloques_Datos];
        a = 0;
        for(int i=((256*this.Cantidad_FCB)+3*this.Cantidad_Bloques_FAT);i<bytes.length;i++){
            datos_bytes[a++] = bytes[i];
        }
        Println("FileSystem.CargarFS: Cantidad de datos: "+datos_bytes.length);
        //---------------------------------------------------
        //---------------------------------------------------
        // inicializar Datos
        boolean se_cargo = false;
        try{
            for(int i=0;i<this.Cantidad_Bloques_Datos;i++){
                byte[] dat = new byte[this.Tamano_Bloque];
                int c = 0;
                for(int j=this.Tamano_Bloque*i;c<this.Tamano_Bloque;j++){
                    dat[c++] = datos_bytes[j];
                }
                se_cargo = this.setDato(i, dat);
                if(!se_cargo){
                    PrintDebug("FileSystem.CargarFS: No se logró cargar un bloque de datos. i = "+i);
                    return false;
                }
            }
        }catch(Exception exp){
            //si se cargó significa que era el último...
            if(!se_cargo){
                PrintDebug("FileSystem.CargarFS: No se logró cargar los datos");
            return false;
            }
        }
        //---------------------------------------------------
        return true;
    }

    /***********************************************
     * Método: getDirectorio
     * Uso: Obtiene el FCB del Directorio[i]
     * @param i contiene el índice que se desea del directorio
     * @return LinkedList<Integer> contiene una lista de índices
     *      de todos los hijos
    ***********************************************/
    public FCB getDirectorio(int i){
        try{
            return Directorio[i];
        }catch(Exception exp){
            return null;
        }
    }

    /***********************************************
     * Método: Completar_a_tamano
     * Uso: completa una cadena al tamaño i deseado
    ***********************************************/
    public String Completar_a_tamano(int i, String value){
        if(value.length()==i){
            return value;
        }
        if(value.length()<i){
            String cadena =value;
            for(int j=value.length();j<i;j++){
                cadena+=" ";
            }
            return cadena;
        }
        if(value.length()>i){
            return value.substring(0, i);
        }
        return "";
    }

    /***********************************************
     * Método: getFirstDirectorioEmpty
     * Uso: Obtiene el FCB del Directorio[i]
     * @param i contiene el índice que se desea del directorio
     * @return LinkedList<Integer> contiene una lista de índices
     *      de todos los hijos
    ***********************************************/
    public int getFirstDirectorioEmpty(){
        try{
            for(int i=0;i<this.Directorio.length;i++){
                if(this.Directorio[i].isEmpty()){
                    return i;
                }
            }
        }catch(Exception exp){
            return -1;
        }
        return -1;
    }

    /***********************************************
     * Método: getFirstFATEmpty
     * Uso: Devuelve una lista de índices del fat vacíos
     * @param cantidad contiene el tamaño de la lista resultante
     * @return LinkedList<Integer> contiene una lista de índices
    ***********************************************/
    public LinkedList<Integer> getFirstFATEmpty(int cantidad){
        LinkedList<Integer> lista = new LinkedList<Integer>();
        try{
            for(int i=0;i<this.FAT.length;i++){
                if(this.FAT[i].trim().compareTo("-1")==0){
                    lista.add(i);
                    if(lista.size()>=cantidad){
                        return lista;
                    }
                }
            }
        }catch(Exception exp){
            return null;
        }
        return null;
    }

    /***********************************************
     * Método: getAllDatos_string
     * Uso: Devuelve todos los datos direccionados con el índice i
     * @param i con el índice
     * @return String con todo el contenido
    ***********************************************/
    public String getAllDatos_string(int i, int cant_caracteres){
        int a = i;
        String cadena = "";

        while(true){
            String c = new String(this.getDato(a));
            cadena+= c;
            a = this.getFAT_value(a);
            if(a==-1){
                break;
            }
        }
        try{
            cadena = cadena.substring(0, cant_caracteres);
        }catch(Exception exp){
            return "";
        }
        return cadena;
    }

    /***********************************************
     * Método: getTamanoFile
     * Uso: Permite obtener el tamaño de un archivo o carpeta
     * @param i con el índice
     * @return String con todo el contenido
    ***********************************************/
    public int getTamanoFile(int indice){

        FCB fcb = this.getDirectorio(indice);
        if(fcb == null){
            return -1;
        }
        if(fcb.isArchivo()){
            return fcb.getTamano_real_value();
        }
        else if(fcb.isCarpeta()){
            int suma = 0;
            LinkedList<Integer> hijos = this.getAllKids(indice);
            for(int i = 0;i<hijos.size();i++){
                suma+=this.getTamanoFile(hijos.get(i));
            }
            return suma;
        }
        return 0;
    }


    /***********************************************
     * Método: getAllDatos_string
     * Uso: Devuelve todos los datos direccionados con el índice i
     * @param i con el índice
     * @return String con todo el contenido
    ***********************************************/
    public byte[] getAllDatos_bytes(int i, int cant_caracteres){
        int a = i;

        byte[] cadena = new byte[cant_caracteres];
        try{
            int d = 0;
            while(true){
                byte[] c = (this.getDato(a));
                for(int j=0;j<c.length;j++){
                    cadena[d++] = c[j];
                }
                a = this.getFAT_value(a);
                if(a==-1){
                    break;
                }
            }
        }catch(Exception exp){
            return cadena;
        }
        return cadena;
    }

    /***********************************************
     * Método: getFAT_value
     * Uso:
     * @param i con el índice
     * @return String el contenido de FAT[i]
    ***********************************************/
    public int getFAT_value(int i){
        try{
            return Integer.parseInt(FAT[i].trim());
        }
        catch(Exception exp){
            return -1;
        }
    }

    /***********************************************
     * Método: getDato
     * Uso:
     * @param i con el índice
     * @return String el contenido de Datos[i]
    ***********************************************/
    public byte[] getDato(int i){
        try{
            return this.Datos[i];
        }
        catch(Exception exp){
            return null;
        }
    }

    /***********************************************
     * Método: Wait
     * Uso: acción de semáforo para evitar que otro proceso entre
    ***********************************************/
    public void Wait(){
        try {
            this.wait();
        } catch (Exception ex) {
            System.out.println("FileSystem.Wait error con semáforos de java");
        }
    }

    /***********************************************
     * Método: Signal
     * Uso: acción de semáforo para evitar que otro proceso entre
    ***********************************************/
    public void Signal(){
        try {
            this.notify();
        } catch (Exception ex) {
            System.out.println("FileSystem.Wait error con semáforos de java");
        }
    }

    /***********************************************
     * Método: defaultFAT
     * Uso: reinicia el valor de una posición del FAT
     * @param i contiene el índice que se desea de FAT
     * @return boolean que dice si se pudo o no
    ***********************************************/
    public boolean defaultFAT(int i){
        try{
            FAT[i] = "-1 ";
        }catch(Exception exp){
            //no existe el índice
            return false;
        }
        return true;
    }

    /***********************************************
     * Método: defaultDatos
     * Uso: reinicia el valor de una posición de los datos
     * @param i contiene el índice que se desea de los datos
     * @return boolean que dice si se pudo o no
    ***********************************************/
    public boolean defaultDatos(int i){
        try{
            byte[] cad = new byte[this.Tamano_Bloque];
            for(int j=0;j<this.Tamano_Bloque;j++){
                cad[j] = (new Byte(" ")).byteValue();
            }
            Datos[i] = cad;
        }catch(Exception exp){
            //no existe el índice o hubo un error en el ciclo
            return false;
        }
        return true;
    }

    /***********************************************
     * Método: defaultFS
     * Uso: Crea un File System default.
    ***********************************************/
    public void defaultFS() {

        //inicializar por default los File Control Blocks
        for(int i=0;i<this.Cantidad_FCB;i++){
            this.Directorio[i] = new FCB();
        }
        //inicializar por default el FAT y los datos
        for(int i=0;i<this.Cantidad_Bloques_FAT;i++){
            this.defaultFAT(i);
        }
        //inicializar por default los datos
        for(int i=0;i<this.Cantidad_Bloques_Datos;i++){
            this.defaultDatos(i);
        }

        //el Directorio[0] siempre es "~" y es carpeta ---
        Directorio[0].setId(0);
        Directorio[0].setNombre("~");
        Directorio[0].setCarpeta_archivo("c");
        //------------------------------------------------
    }

    /***********************************************
     * Método: setFAT
     * Uso: setea el valor de el índice del FAT
     * @param i contiene el índice que se desea de FAT
     * @return boolean que dice si se pudo o no
    ***********************************************/
    public boolean setFAT(int i, String contenido){
        //validaciones:
        //el contenido puede ser: "-1","0-999","   "
        if(contenido.length()>3){
            return false;
        }
        try{
            Integer.parseInt(contenido.trim());
        }
        catch(Exception exp){
            if(!contenido.trim().isEmpty()){
                return false;
            }
        }
        String a = contenido;
        for(int j=contenido.length();j<3;j++){
            a+=" ";
        }
        try{
            FAT[i] = a;
        }catch(Exception exp){
            //no existe el índice
            return false;
        }
        return true;
    }

    /***********************************************
     * Método: setDato
     * Uso: setea el valor de el índice del FAT
     * @param i contiene el índice que se desea de FAT
     * @return boolean que dice si se pudo o no
    ***********************************************/
    public boolean setDato(int i, byte[] contenido){
        //validaciones:
        //el contenido puede ser sólo de tamaño 1024
        if(contenido.length>this.Tamano_Bloque){
            PrintDebug("FileSystem.setDato: Tamaño incorrecto. No se logró cargar en el índice: "+i+" tamaño_contenido: "+contenido.length);
            return false;
        }
        if(contenido.length<this.Tamano_Bloque){
            byte[] cont = new byte[this.Tamano_Bloque];
            for(int a=0;a<contenido.length;a++){
                cont[a] = contenido[a];
            }
            try{
                Datos[i] = cont;
            }catch(Exception exp){
                //no existe el índice
                PrintDebug("FileSystem.setDato: No se logró cargar en el índice: "+i+" tamaño_contenido: "+contenido.length);
                return false;
            }
        }
        else{
            try{
                Datos[i] = contenido;
            }catch(Exception exp){
                //no existe el índice
                PrintDebug("FileSystem.setDato: No se logró cargar en el índice: "+i+" tamaño_contenido: "+contenido.length);
                return false;
            }
        }
        return true;
    }

    /***********************************************
     * Método: getAllKids
     * Uso: obtiene todos los hijos que esten direccionados
     *      de la carpeta en el índice i del directorio
     * @param i contiene el índice que se desea del directorio
     * @return LinkedList<Integer> contiene una lista de índices
     *      de todos los hijos
    ***********************************************/
    public LinkedList<Integer> getAllKids(int i){
        LinkedList<Integer> lista_retorno = new LinkedList<Integer>();
        FCB fcb;
        try{
            fcb = this.Directorio[i];
        }catch(Exception exp){
            PrintDebug("FileSystem.getAllKids: indice incorrecto: "+i);
            return null;
        }
        //revisar si realmente es una carpeta para ver sus hijos
        if(fcb.isCarpeta()==false){
            PrintDebug("FileSystem.getAllKids: el FCB del índice no es carpeta: "+fcb.getNombre());
            return new LinkedList<Integer>();
        }
        int primer_hijo_indice = fcb.getPrimer_bloque_de_datos_value();
        FCB fcb_hijo;
        try{
            fcb_hijo = this.Directorio[primer_hijo_indice];
            if(fcb_hijo==null){
                //PrintDebug("FileSystem.getAllKids: carpeta: ."+fcb.getNombre()+". no tiene hijos");
                return new LinkedList<Integer>();
            }
            lista_retorno.add(primer_hijo_indice);
        }catch(Exception exp){
            //no tiene hijos...
            //PrintDebug("FileSystem.getAllKids: carpeta: ."+fcb.getNombre()+". no tiene hijos");
            return new LinkedList<Integer>();
        }
        //------------------------------------------------
        //     buscar todos los hermanos
        //------------------------------------------------
        FCB fcb_hermano = fcb_hijo;
        while(true){
            int fcb_hermano_indice = fcb_hermano.getId_hermano_value();
            //PrintDebug("FileSystem.getAllKids: hermano: "+fcb_hermano_indice);
            try{
                fcb_hermano = Directorio[fcb_hermano_indice];
                lista_retorno.add(fcb_hermano_indice);
            }catch(Exception exp){
                break;
            }
        }
        return lista_retorno;
    }

    /***********************************************
     * Método: isHijo
     * Uso: dice si en el índice padre se tiene el hijo
    ***********************************************/
    public boolean isHijo(int id_padre, String nombre_hijo){
        LinkedList<Integer> lista_hijos = this.getAllKids(id_padre);
        if(lista_hijos.size()==0){
            return false;
        }
        for(int i=0;i<lista_hijos.size();i++){
            FCB fcb = this.Directorio[lista_hijos.get(i)];
            if(fcb.getNombre().compareTo(nombre_hijo)==0){
                return true;
            }
        }
        return false;
    }

    /***********************************************
     * Método: getIndiceHijo
     * Uso: dice si en el índice padre se tiene el hijo
    ***********************************************/
    public int getIndiceHijo(int id_padre, String nombre_hijo){
        LinkedList<Integer> lista_hijos = this.getAllKids(id_padre);
        if(lista_hijos.size()==0){
            return -1;
        }
        for(int i=0;i<lista_hijos.size();i++){
            FCB fcb = this.Directorio[lista_hijos.get(i)];
            if(fcb.getNombre().compareTo(nombre_hijo)==0){
                return lista_hijos.get(i);
            }
        }
        return -1;
    }

    /***********************************************
     * Método: getIndiceHijo
     * Uso: dice si en el índice padre se tiene el hijo
    ***********************************************/
    public boolean existNameKid(int id_padre, String nombre_hijo){
        int indice = this.getIndiceHijo(id_padre, nombre_hijo);
        if(indice == -1){
            return false;
        }
        return true;
    }

    /***********************************************
     * Método: existPath
     * Uso: revisa si existe el path ingresado
    ***********************************************/
    public boolean existPath(String path, int padre_inicio){
        String[] arreglo = path.split("/");
        int inicio = -1;
        if(arreglo[0].compareTo("~")==0){
            inicio = 0;
        }
        else{
            inicio = padre_inicio;
            try{
                inicio = CPU.fileSystem.getIndiceHijo(inicio, arreglo[0]);
            }
            catch(Exception exp){
                inicio = padre_inicio;
            }
        }
        boolean error = false;
        for(int i=1;i<arreglo.length;i++){
            if(CPU.fileSystem.existNameKid(inicio, arreglo[i]) == true){
                inicio = CPU.fileSystem.getIndiceHijo(inicio, arreglo[i]);
            }
            else{
                error = true;
                break;
            }
        }
        return (!error);
    }

    /***********************************************
     * Método: existPath
     * Uso: revisa si existe el path ingresado
    ***********************************************/
    public int getPath_id(String path, int padre_inicio){
        String[] arreglo = path.split("/");
        int inicio = -1;
        if(arreglo[0].compareTo("~")==0){
            inicio = 0;
        }
        else{
            inicio = padre_inicio;
            try{
                inicio = CPU.fileSystem.getIndiceHijo(inicio, arreglo[0]);
            }
            catch(Exception exp){
                inicio = padre_inicio;
            }
        }
        boolean error = false;
        for(int i=1;i<arreglo.length;i++){
            if(CPU.fileSystem.existNameKid(inicio, arreglo[i]) == true){
                inicio = CPU.fileSystem.getIndiceHijo(inicio, arreglo[i]);
            }
            else{
                error = true;
                break;
            }
        }
        return inicio;
    }

    /***********************************************
     * Método: setFat_Datos
     * Uso:
     * @param vacios contiene una lista con los índices
     *      en los cuales guardar la información
     * @param cadena contiene la cadena a guardar
    ***********************************************/
    public boolean setFat_Datos(LinkedList<Integer> vacios, byte[] datos){
        //el último fat se asigna con "   "
        for(int i=0;i<vacios.size()-1;i++){
            this.setFAT(vacios.get(i), ""+vacios.get(i+1));
            //Println("FileSystem.setFat_Datos: Asignando fat i="+vacios.get(i)+" contiene: ."+this.FAT[vacios.get(i)]+".");
        }
        this.setFAT(vacios.get(vacios.size()-1), "   ");
        //Println("FileSystem.setFat_Datos: Asignando fat i="+vacios.get(vacios.size()-1)+" contiene: ."+this.FAT[vacios.get(vacios.size()-1)]+".");
        //Println("FileSystem.setFat_Datos: vacios: "+vacios.toString());
        //Println("FileSystem.setFat_Datos: tamaño de datos a setear: "+datos.length);
        //asignar datos
        for(int i=0;i<vacios.size();i++){
            byte[] dat = new byte[this.Tamano_Bloque];
            int c = 0;
            for(int j=this.Tamano_Bloque*i;(c<this.Tamano_Bloque && j<datos.length);j++){
                byte w = datos[j];
                dat[c++] = w;
            }
            this.setDato(vacios.get(i), dat);
        }
        return true;
    }

    /***********************************************
     * Método: enoughSpace
     * Uso: Revisa en el FAT si hay espacio para el archivo
    ***********************************************/
    public boolean enoughSpace(String cadena){
        int cant_fat_libres = this.getFreeSpace()/this.Tamano_Bloque;
        int fat_requeridos_archivo = (cadena.length() / this.Tamano_Bloque) + 1;

        if(fat_requeridos_archivo>cant_fat_libres){
            return false;
        }
        return true;
    }

    /***********************************************
     * Método: enoughSpace
     * Uso: Revisa en el FAT si hay espacio para el archivo
    ***********************************************/
    public boolean enoughSpace(int requerido){
        int cant_fat_libres = this.getFreeSpace()/this.Tamano_Bloque;
        int fat_requeridos_archivo = (requerido / this.Tamano_Bloque) + 1;

        if(fat_requeridos_archivo>cant_fat_libres){
            return false;
        }
        return true;
    }

    /***********************************************
     * Método: cp_host_this
     * Uso: copia un archivo del path de host al de este SO
    ***********************************************/
    public boolean cp_host_this(String fuente, String destino){
        
        try{
            byte[] bytes;
            RandomAccessFile archivo;
            File file;
            try {
                file = new File(fuente);
                archivo = new RandomAccessFile(fuente,"r");
                bytes = new byte[(int)archivo.length()];
                archivo.readFully(bytes);
            } catch (Exception ex) {
                PrintDebug("FileSystem.cp_host_this: No se pudo abrir el archivo: "+fuente);
                return false;
            }

            int inicio = -1;
            if(destino.split("/")[0].compareTo("~")==0){
                inicio = 0;
            }else{
                inicio = Shell.getRoot();
            }
            if(this.existPath(destino, inicio) == false){
                Println("FileSystem.cp_host_this: No existe el archivo: "+destino+" en el SO host");
                return false;
            }
            //ya están validados los 2 paths
            //String cadena_origen = this.getFile(fuente);
            if(bytes == null){
                return false;
            }
            if(bytes.length==0){
                //cadena_origen = " ";
            }
            if(enoughSpace(bytes.length)==false){
                Println("FileSystem.cp_host_this: No hay espacio suficiente en FS para el archivo");
                return false;
            }
            int id_vacio = this.getFirstDirectorioEmpty();
            int cant_fat_requeridos = (bytes.length / this.Tamano_Bloque) + 1;
            LinkedList<Integer> id_fat_vacio = this.getFirstFATEmpty(cant_fat_requeridos);
            if(id_fat_vacio.size()!=cant_fat_requeridos){
                Println("FileSystem.cp_host_this: No se pudo asignar fat a todos los datos");
                return false;
            }

            //asignar el fat y los datos
            if(this.setFat_Datos(id_fat_vacio,bytes) == false){
                Println("FileSystem.cp_host_this: No se pudo asignar fat a todos los datos");
                return false;
            }
            int id_path_destino = getPath_id(destino,Shell.getRoot());
            if(this.isNombreValido(file.getName(), id_path_destino) == false){
                Println("FileSystem.cp_host_this: Nombre del archivo no válido: "+file.getName()+". Revise que no exista o que sea alpha-numérico");
                return false;
            }
            FCB fcb = this.getDirectorio(id_vacio);
            fcb.reset();
            fcb.setId(id_vacio);
            fcb.setNombre(file.getName());
            fcb.setPrimer_bloque_de_datos(""+id_fat_vacio.getFirst());
            fcb.setId_padre(id_path_destino);
            try {
                fcb.setTamano_real("" + archivo.length());
            } catch (Exception ex) {
                Logger.getLogger(FileSystem.class.getName()).log(Level.SEVERE, null, ex);
            }

            FCB fcb_padre = this.getDirectorio(id_path_destino);
            LinkedList<Integer> hijos = this.getAllKids(id_path_destino);
            if(hijos.isEmpty()){
                fcb_padre.setPrimer_bloque_de_datos(id_vacio+"");
            }
            else{
                this.getDirectorio(hijos.getLast()).setId_hermano(id_vacio);
            }
        }catch(Exception exp){
            return false;
        }
        return true;
    }

    /***********************************************
     * Método: cp_this_this
     * Uso: copia un archivo del path de host al de este SO
    ***********************************************/
    public boolean cp_this_this(String fuente, String destino){

        byte[] bytes;
        try{

            int inicio_1 = -1;
            if(fuente.split("/")[0].compareTo("~")==0){
                inicio_1 = 0;
            }else{
                inicio_1 = Shell.getRoot();
            }
            if(this.existPath(fuente, inicio_1) == false){
                Println("FileSystem.cp_this_this: No existe el archivo: "+fuente+" en el SO host");
                return false;
            }

            int inicio = -1;
            if(destino.split("/")[0].compareTo("~")==0){
                inicio = 0;
            }else{
                inicio = Shell.getRoot();
            }
            if(this.existPath(destino, inicio) == false){
                Println("FileSystem.cp_this_this: No existe el archivo: "+destino+" en el SO host");
                return false;
            }
            //ya están validados los 2 paths
            int id_fuente = this.getPath_id(fuente, inicio_1);
            int id_fuente_primer_bloque = this.getDirectorio(id_fuente).getPrimer_bloque_de_datos_value();
            int tamano_real = this.getDirectorio(id_fuente).getTamano_real_value();

            bytes = this.getAllDatos_bytes(id_fuente_primer_bloque,tamano_real);

            if(bytes.length==0){
                //cadena_origen = " ";
            }
            if(enoughSpace(bytes.length)==false){
                Println("FileSystem.cp_host_this: No hay espacio suficiente en FS para el archivo");
                return false;
            }
            int id_vacio = this.getFirstDirectorioEmpty();
            int cant_fat_requeridos = (bytes.length / this.Tamano_Bloque) + 1;
            LinkedList<Integer> id_fat_vacio = this.getFirstFATEmpty(cant_fat_requeridos);
            if(id_fat_vacio.size()!=cant_fat_requeridos){
                Println("FileSystem.cp_host_this: No se pudo asignar fat a todos los datos");
                return false;
            }

            //asignar el fat y los datos
            if(this.setFat_Datos(id_fat_vacio,bytes) == false){
                Println("FileSystem.cp_host_this: No se pudo asignar fat a todos los datos");
                return false;
            }
            int id_path_destino = getPath_id(destino,inicio);
            if(this.isNombreValido(this.getDirectorio(id_fuente).getNombre(), id_path_destino) == false){
                Println("FileSystem.cp_host_this: Nombre del archivo no válido: "+this.getDirectorio(id_fuente).getNombre()+". Revise que no exista o que sea alpha-numérico");
                return false;
            }

            FCB fcb = this.getDirectorio(id_vacio);
            fcb.reset();
            fcb.setId(id_vacio);
            fcb.setNombre(this.getDirectorio(id_fuente).getNombre());
            fcb.setPrimer_bloque_de_datos(""+id_fat_vacio.getFirst());
            fcb.setId_padre(id_path_destino);
            fcb.setTamano_real(""+bytes.length);

            FCB fcb_padre = this.getDirectorio(id_path_destino);
            LinkedList<Integer> hijos = this.getAllKids(id_path_destino);
            if(hijos.isEmpty()){
                fcb_padre.setPrimer_bloque_de_datos(id_vacio+"");
            }
            else{
                this.getDirectorio(hijos.getLast()).setId_hermano(id_vacio);
            }
            
        }catch(Exception exp){
            return false;
        }

        return true;
    }

    /***********************************************
     * Método: mv_this_this
     * Uso: mueve un archivo del path de host al de este SO
    ***********************************************/
    public boolean mv_this_this(String fuente, String destino){

        try{
            int inicio_1 = -1;
            if(fuente.split("/")[0].compareTo("~")==0){
                inicio_1 = 0;
            }else{
                inicio_1 = Shell.getRoot();
            }
            if(this.existPath(fuente, inicio_1) == false){
                Println("FileSystem.mv_this_this: No existe el archivo: "+fuente+" en el SO host");
                return false;
            }

            int inicio = -1;
            if(destino.split("/")[0].compareTo("~")==0){
                inicio = 0;
            }else{
                inicio = Shell.getRoot();
            }
            if(this.existPath(destino, inicio) == false){
                Println("FileSystem.mv_this_this: No existe el archivo: "+destino+" en el SO host");
                return false;
            }
            //ya están validados los 2 paths
            int id_fuente = this.getPath_id(fuente, inicio_1);
            int id_path_destino = getPath_id(destino,inicio);

            if(this.isNombreValido(this.getDirectorio(id_fuente).getNombre(), id_path_destino) == false){
                Println("FileSystem.mv_host_this: Nombre del archivo no válido: "+this.getDirectorio(id_fuente).getNombre()+". Nombre ya existente en: "+this.getPath(id_path_destino));
                return false;
            }
            //obtener nuevos hermanos... direccionar del último a este archivo
            // si no tiene hermanos, entonces direccionar al padre...
            FCB fcb_fuente = this.getDirectorio(id_fuente);
            LinkedList<Integer> hermanos = this.getAllKids(fcb_fuente.getId_padre_value());
            int referenciar = -1;
            if(hermanos.isEmpty()){
                referenciar = fcb_fuente.getId_padre_value();
            }
            else{
                int a = hermanos.indexOf((Integer)id_fuente);
                if(a == 0){
                    referenciar = fcb_fuente.getId_padre_value();
                    this.getDirectorio(referenciar).setPrimer_bloque_de_datos(""+fcb_fuente.getId_hermano_value());
                }
                else{
                    referenciar = hermanos.get(a-1);
                    this.getDirectorio(referenciar).setId_hermano(fcb_fuente.getId_hermano_value());
                }
            }

            // guardar en destino
            LinkedList<Integer> hermanos_destino = this.getAllKids(id_path_destino);
            int referenciar_2 = -1;
            if(hermanos_destino.isEmpty()){
                referenciar_2 = id_path_destino;
                this.getDirectorio(referenciar_2).setPrimer_bloque_de_datos(""+id_fuente);
            }
            else{
                referenciar_2 = hermanos_destino.getLast();
                this.getDirectorio(referenciar_2).setId_hermano(id_fuente);
            }
            fcb_fuente.setId_hermano(-1);
            fcb_fuente.setId_padre(id_path_destino);
        }catch(Exception exp){
            return false;
        }


        return true;
    }

    /***********************************************
     * Método: mv_host_this
     * Uso: mueve un archivo del path de host al de este SO
    ***********************************************/
    public boolean mv_host_this(String fuente, String destino){

        //no debería dejar mover del host a este...
        return true;
    }

    /***********************************************
     * Método: cp_this_host
     * Uso: copia un archivo del path de host al de este SO
    ***********************************************/
    public boolean cp_this_host(String fuente, String destino){
        try{
            int inicio = -1;
            if(fuente.split("/")[0].compareTo("~")==0){
                inicio = 0;
            }else{
                inicio = Shell.getRoot();
            }
            if(this.existPath(fuente, inicio) == false){
                Println("FileSystem.cp_this_host: No existe el archivo: "+fuente+" en el SO");
                return false;
            }
            int indice = this.getPath_id(fuente, inicio);
            int primer_bloque = this.getDirectorio(indice).getPrimer_bloque_de_datos_value();
            int tamano_real = this.getDirectorio(indice).getTamano_real_value();
            byte[] contenido = this.getAllDatos_bytes(primer_bloque,tamano_real);
            Println("FileSystem.cp_this_host: tamaño del contenido a guardar: "+contenido.length);
            String path = destino + this.getDirectorio(indice).getNombre();
            Println("FileSystem.cp_this_host: path en donde guardar: "+path);
            return this.printToFile(path,contenido);

        }catch(Exception exp){
            Println("FileSystem.cp_this_host: No se pudo copiar el archivo: "+fuente+" al destino: "+destino);
        }
        return false;
        
    }


    /***********************************************
     * Método: mv_this_host
     * Uso: copia un archivo del path de host al de este SO
    ***********************************************/
    public boolean mv_this_host(String fuente, String destino){
        //copiar y rm
        if(this.cp_this_host(fuente, destino)== false){
            return false;
        }
        try{
            int inicio = -1;
            if(fuente.split("/")[0].compareTo("~")==0){
                inicio = 0;
            }else{
                inicio = Shell.getRoot();
            }
            if(this.existPath(fuente, inicio) == false){
                Println("FileSystem.mv_this_host: No existe el archivo: "+fuente+" en el SO");
                return false;
            }
            int indice = this.getPath_id(fuente, inicio);
            return this.rm(indice, false);
        }catch(Exception exp){
            Println("FileSystem.mv_this_host: No se pudo mover el archivo: "+fuente+" al destino: @"+destino);
        }
        return false;
    }

    /***********************************************
     * Método: getPath
     * Uso: retorna el path completo de determinado FCB
    ***********************************************/
    public String getPath(int id){

        FCB fcb = this.getDirectorio(id);
        if(fcb == null){
            return "";
        }
        String cadena = fcb.getNombre();
        if(fcb.getId_padre_value()!=-1){
            cadena = this.getPath(fcb.getId_padre_value())+"/"+cadena;
        }
        return cadena;
    }

    /***********************************************
     * Método: getFreeSpace
     * Uso: retorna el espacio libre buscando en el FAT
    ***********************************************/
    public int getFreeSpace(){
        int contador = 0;

        for(int i=0;i<this.FAT.length;i++){
            try{
                int a = Integer.parseInt(this.FAT[i].trim());
                if(a==-1){
                    contador++;
                }else{
                    //Println("FileSystem.getFreeSpace: este índice no está vacío: "+i);
                }
            }
            catch(Exception exp){
                if(this.FAT[i].trim().isEmpty()){
                    //indica fin del archivo, pero sí está en uso
                }
                else{
                    contador++;
                }
            }
        }

        return contador*this.Tamano_Bloque;
    }

    /***********************************************
     * Método: isNombreValido
     * Uso: retorna el espacio libre buscando en el FAT
    ***********************************************/
    public boolean isNombreValido(String nuevo_nombre){
        String letter="[a-zA-Z]";
        String digit="\\d";
        String ident=letter+"("+letter+"|"+digit+"|\\.)*";
        //revisión de que el nombre sea correcto
        if(nuevo_nombre.matches(ident) == false || nuevo_nombre.length()>=32){
            return false;
        }
        return true;
    }

    /***********************************************
     * Método: isNombreValido
     * Uso: retorna el espacio libre buscando en el FAT
    ***********************************************/
    public boolean isNombreValido(String nuevo_nombre, int id){
        String letter="[a-zA-Z]";
        String digit="\\d";
        String ident=letter+"("+letter+"|"+digit+"|\\.)*";
        //revisión de que el nombre sea correcto
        if(nuevo_nombre.matches(ident) == false || nuevo_nombre.length()>=32){
            return false;
        }
        if(this.existNameKid(id, nuevo_nombre) == true){
            return false;
        }
        return true;
    }

    /***********************************************
     * Método: getNonFreeSpace
     * Uso: retorna el espacio no libre buscando en el FAT
    ***********************************************/
    public int getNonFreeSpace(){
        int freeSpace = getFreeSpace();

        int totalSpace = getTotalFreeSpace();

        return (totalSpace-freeSpace);
    }

    /***********************************************
     * Método: getTotalFreeSpace
     * Uso: retorna el espacio total buscando en el FAT
    ***********************************************/
    public int getTotalFreeSpace(){
        return this.Cantidad_Bloques_FAT*this.Tamano_Bloque;
    }

    /***********************************************
     * Método: rm
     * Uso: retorna el espacio total buscando en el FAT
    ***********************************************/
    public boolean rm(int indice, boolean imprimir_borrando){

        //borrar el FAT
        //reset del Directorio[indice]
        FCB fcb = this.getDirectorio(indice);
        if(imprimir_borrando == true){
            Println("Borrando "+this.getPath(indice));
        }

        if(fcb == null){
            return false;
        }
        LinkedList<Integer> hermanos = this.getAllKids(fcb.getId_padre_value());
        int referenciar = -1;
        if(hermanos.isEmpty()){
            referenciar = fcb.getId_padre_value();
        }
        else{
            int a = hermanos.indexOf((Integer)indice);
            if(a == 0){
                referenciar = fcb.getId_padre_value();
                this.getDirectorio(referenciar).setPrimer_bloque_de_datos(""+fcb.getId_hermano_value());
            }
            else{
                referenciar = hermanos.get(a-1);
                this.getDirectorio(referenciar).setId_hermano(fcb.getId_hermano_value());
            }
        }


        if(fcb.isCarpeta()){
            //guardar los hijos que tenía
            LinkedList<Integer> hijos = this.getAllKids(indice);
            for(int i=0;i<hijos.size();i++){
                rm(hijos.get(i), imprimir_borrando);
            }
            rm_FAT(fcb.getPrimer_bloque_de_datos_value());
            fcb.reset();

        }else if(fcb.isArchivo()){
            //borrar el FAT y reiniciar
            rm_FAT(fcb.getPrimer_bloque_de_datos_value());
            fcb.reset();
        }
        else{
            return false;
        }

        return true;
    }

    /***********************************************
     * Método: print_folder
     * Uso: retorna el espacio total buscando en el FAT
    ***********************************************/
    public boolean print_folder(int indice){

        FCB fcb = this.getDirectorio(indice);
        Println(""+this.getPath(indice));
        if(fcb == null){
            return false;
        }
        if(fcb.isCarpeta()){
            //guardar los hijos que tenía
            LinkedList<Integer> hijos = this.getAllKids(indice);
            for(int i=0;i<hijos.size();i++){
                print_folder(hijos.get(i));
            }    
        }else if(fcb.isArchivo()){
            return true;
        }
        else{
            return false;
        }
        return true;
    }

    /***********************************************
     * Método: rm_FAT
     * Uso: Borra el fat recursivamente
    ***********************************************/
    public boolean rm_FAT(int indice){

        int num = this.getFAT_value(indice);
        this.setFAT(indice, "-1 ");
        if(num == -1){
            return true;
        }
        return rm_FAT(num);

    }

    /***********************************************
     * Método: getPath
     * Uso: retorna el path completo de determinado FCB
    ***********************************************/
    public FCB getDirectorio_clone(int i){
        FCB fcb = this.getDirectorio(i);
        FCB fcb_nuevo = new FCB();
        fcb_nuevo.setCarpeta_archivo(fcb.getCarpeta_archivo());
        fcb_nuevo.setNombre(fcb.getNombre());
        fcb_nuevo.setId(fcb.getId());
        fcb_nuevo.setId_padre(fcb.getId_padre());
        fcb_nuevo.setId_hermano(fcb.getId_hermano());
        fcb_nuevo.setFecha_creacion(fcb.getFecha_creacion());
        fcb_nuevo.setFecha_modificacion(fcb.getFecha_modificacion());
        fcb_nuevo.setPrimer_bloque_de_datos(fcb.getPrimer_bloque_de_datos());
        fcb_nuevo.setTamano_real(fcb.getTamano_real());
        return fcb_nuevo;
    }

    /***********************************************
     * Método: toString
     * Uso: Devuelve una cadena con el string que representa
     *      el FileSystem:
     *      -> Directorio
     *      -> FAT
     *      -> Datos
    ***********************************************/
    public String toString(){

        String directorio = "";
        String fat = "";
        String datos = "";
        try{
            //obtener el string del directorio completo
            for(int i=0;i<this.Cantidad_FCB;i++){
                directorio+=this.Directorio[i].toString();
            }

            //obtener el string del fat completo
            for(int i=0;i<this.Cantidad_Bloques_FAT;i++){
                fat+=this.FAT[i];
            }

            //obtener el string de los datos completos
            for(int i=0;i<this.Cantidad_Bloques_Datos;i++){
                datos+=this.Datos[i];
            }
        }
        catch(Exception exp){
            return "";
        }
        
        return directorio + fat + datos;
    }

    /***********************************************
     * Método: toBytes
     * Uso: Devuelve una cadena con el string que representa
     *      el FileSystem:
     *      -> Directorio
     *      -> FAT
     *      -> Datos
    ***********************************************/
    public byte[] toBytes(){

        //LinkedList<Byte> retorno = new LinkedList<Byte>();
        byte[] retorno = new byte[1024*1024];
        int contador = 0;
        String directorio = "";
        String fat = "";

        try{
            //obtener el string del directorio completo
            for(int i=0;i<this.Cantidad_FCB;i++){
                directorio+=this.Directorio[i].toString();
            }

            for(int i=0;i<directorio.getBytes().length;i++){
                retorno[contador++] = (directorio.getBytes()[i]);
            }

            //obtener el string del fat completo
            for(int i=0;i<FileSystem.Cantidad_Bloques_FAT;i++){
                fat+=this.FAT[i];
            }

            for(int i=0;i<fat.getBytes().length;i++){
                retorno[contador++] = (fat.getBytes()[i]);
            }

            //obtener el string de los datos completos
            for(int i=0;i<FileSystem.Cantidad_Bloques_Datos;i++){
                for(int j=0;j<this.Tamano_Bloque;j++){
                    retorno[contador++] = (Datos[i][j]);
                }
            }


        }
        catch(Exception exp){
            return null;
        }
        
        return retorno;
    }

     /***********************************************
     * Método: PrintDebug
     * Uso: Imprime sólo si está activado el debug del file system
    ***********************************************/
    public static void PrintDebug(String cadena){
        if(FileSystem.debug){
            System.out.println(cadena);
        }
    }


    /***********************************************
     * Método: Println
     * Uso: Imprime sólo si está activado el debug del file system
    ***********************************************/
    public void Println(String cadena){
        System.out.println(cadena);
    }

    /***********************************************
     * Método: getFile
     * Uso: Imprime sólo si está activado el debug del file system
    ***********************************************/
    public String getFile(String path){
        String cadena = "";
        Scanner scan_file;
        //Println("FileSystem.getFile: Path: ."+path+".");
        try {
            scan_file = new Scanner(new File(path));
            while(scan_file.hasNextLine()){
                cadena+= scan_file.nextLine();
                if(scan_file.hasNextLine()){
                    cadena+="\n";
                }

            }
        } catch (Exception ex) {
            Println("FileSystem.getFile: No se pudo cargar el archivo");
            return null;
        }

        //Println("FileSystem.getFile: Archivo jalado: ."+cadena+".");
        return cadena;
    }

    /***********************************************
     * Método: printToFile
     * Uso: escribe el log con las peteciones de abrir y cerrar archivos
     * @param imprimir contiene lo que se desa imprimir
     * @return boolean diciendo si se guardó el archivo correctamente
    ***********************************************/
    public boolean printToFile(String path, String contenido) {

        File TextFile = new File(path);
        FileWriter TextOut;
        try {
            TextOut = new FileWriter(TextFile, true);
            TextOut.write(contenido);
            TextOut.close();
        } catch (Exception ex) {
            Println("FileSystem.printToFile: No se pudo guardar el archivo en: "+path);
            return false;
        }
        return true;

    }

    /***********************************************
     * Método: printToFile
     * Uso: escribe el log con las peteciones de abrir y cerrar archivos
     * @param imprimir contiene lo que se desa imprimir
     * @return boolean diciendo si se guardó el archivo correctamente
    ***********************************************/
    public boolean printToFile(String path, byte[] contenido) {

        try {
            /*
            File arc = new File(path);
            if(arc.exists() == false){
                FileWriter TextOut;
                TextOut = new FileWriter(arc, true);
                TextOut.write("");
                TextOut.close();
            }
             */
            RandomAccessFile file = new RandomAccessFile(path,"rw");
            Println("FileSystem.printToFile: inicialización del archivo está bien..");
            file.write(contenido);
        } catch (Exception ex) {
            Println("FileSystem.printToFile: No se pudo guardar en el archivo: "+path);
            return false;
        }
        return true;

    }

}